查看原文
其他

Android vold(卷管理)传记

牛晓伟 郭霖
2024-09-27



/   今日科技快讯   /


近日,以“穿越周期”为主题的2024年餐饮产业大会在上海召开。会上,美团核心本地商业CEO王莆中宣布,升级针对餐饮商家的“繁盛计划”,通过数百亿促消费投入、“品牌卫星店”万店返佣计划、餐饮新店扶持计划、数字化专项等举措,共同推进行业创新,释放需求潜力,增强经营信心。


/   作者简介   /


本篇文章来自牛晓伟的投稿,文章主要分享了Android中卷管理相关的知识,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。


原文地址:

https://mp.weixin.qq.com/s/VjcU3s6YcyR5jr3tndov2g


/   前言   /


本篇文章同样延续自述和对话的方式来介绍vold(卷管理进程),通过本文您将了解到它在Android系统中到底起了哪些作用?它是如何监听外部存储设备的热插拔事件?如何管理所有的卷?管理的卷到底有啥用?(基于android13代码分析)


/   我是谁   /


“诸位上眼了!自古以来名人都有传记,我虽然没有它们有名,但我也想有自己的传记,以备后人能记得我。并且在Android系统中我大小也算个有头有脸的‘人物’。”



“呸!不害臊,你也敢自称有名,还敢有自己的传记,我敢肯定非常多的人都不认识你。”


“这位小兄弟,说话别那么刻薄,我虽然没有ActivityManagerService等有名,那不是我不想出名,而是因为我的性格使然,我一出生就是一个demon进程,也就是我是一个‘低调内敛的性格’,我一直都是默默的在幕后工作着,我虽然不咋有名但是我还是有理想的,我希望我的传记能让大家及后人记住我。并且我有很多的兄弟如lmkdlogdinstalld等,它们也出了自己的传记lmkd的传记logd传记installd传记,那我的传记就先从介绍我自己开始吧。”


“诸位客官好啊,我英文名是vold,它是 volume deamon 英文单词的缩写,翻译为中文就是卷守护进程,我的主要工作职责就是管理所有的卷,加密/解密CE、DE类型的目录,对存储设备或卷进行挂载、卸载、格式化等操作。”


“这里的卷是啥子意思吗?卷对我们app进程来说有啥作用?还有能用直白的话说明吗?最好能举例子,这样结合例子大家肯定会对你有深入了解”一个进程问到。


“这位仁兄提了很好的建议,我先解释下卷,卷用正式官方语解释的话:卷通常指的是存储设备或存储介质上的一个独立区域,用于存储文件和数据。在操作系统中,一个硬盘可以被分为多个分区,每个分区可以被格式化为一个独立的卷。”


“用大白话解释:外部存储设备如SD卡,闪存等,它们是可以被划分为一个或多个独立的区域,每个区域对应的是自己的文件系统比如A区是ext2、B区是NTFS (windows默认文件系统),卷只有被挂载后才可以使用。其中卷又有虚拟卷 (EmulatedVolume) 、obb卷 (ObbVolume) 、私有卷 (PrivateVolume)、公有卷 (public Volume)、stub卷 (StubVolume)。”


“卷的作用,你们app应该使用过Environment类的getExternalStoragePublicDirectory等方法,把数据存储到外部存储的文件中(存储区域分为内部存储和外部存储),那这个外部存储其实就是指的一个存储设备上的卷,文件就是存储在这个卷上的。app进程如果需要往外部存储存文件的话,需要从StorageManagerService拿到可用卷,而StorageManagerService存储的卷是从我这拿的。有了可用卷整个外部存储才能使用。”


“加密/解密CE和DE类型目录又是做啥呢?关于CE和DE类型目录下面会有详细介绍,和大家先探讨个问题假如Android设备的内部存储设备不加密的话,会有啥危害?比如你的手机被一个不讲道德的高人捡到,那高人就可以经过一些手段拿到内部存储设备上的数据,这是不是很危险啊,想想当年的啥照事件。而内部存储设备加密又分DE和CE两种,而我vold是有对CE和DE类型目录进行加密和解密的能力的”


我是vold,一个具有root权限的系统native进程,我是存储系统最核心的的成员 (存储系统的文章),主要为app存储文件提供服务,我管理着所有的卷,同时还可以加密/解密 CE和DE类型的目录。


/   我的出生   /


我的父亲是init进程,因为它的子进程是非常非常多的,这么多子进程何时创建、创建之前需要执行哪些命令又更是多上加多,这么多的信息它完全是无招架之力,为了解决这个问题它创建了init脚本语言,哪个子进程需要创建,则配置自己的init脚本语言即可,下面是我的脚本语言:


service vold /system/bin/vold \
        --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
        --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
    class core
    ioprio be 2
    task_profiles ProcessCapacityHigh
    shutdown critical
    group root reserved_disk
    reboot_on_failure reboot,vold-failed

上面的脚本语言会告诉init进程,我的进程名字叫vold,在fork成功后会执行//system/bin/vold/ 可执行文件(后面是需要传递的参数),class core:代表vold进程是属于core组的,而//system/bin/vold/ 可执行文件会执行下面的方法



//文件路径:system/vold/main.cpp
int main(int argc, char** argv) {

    省略代码......

    //初始化VolumeManager、NetlinkManager
    VolumeManager* vm;
    NetlinkManager* nm;

    //解析脚本文件中传递的参数
    parse_args(argc, argv);

    省略代码......

    /* Create our singleton managers */
if (!(vm = VolumeManager::Instance())) {
        LOG(ERROR) << "Unable to create VolumeManager";
exit(1);
    }

if (!(nm = NetlinkManager::Instance())) {
        LOG(ERROR) << "Unable to create NetlinkManager";
exit(1);
    }

if (android::base::GetBoolProperty("vold.debug"false)) {
        vm->setDebug(true);
    }

if (vm->start()) {
        PLOG(ERROR) << "Unable to start VolumeManager";
exit(1);
    }

    VoldConfigs configs = {};

    省略代码......

    //VoldNativeService它是一个binder服务,start方法会把它发布到ServiceManager中
if (android::vold::VoldNativeService::start() != android::OK) {
        LOG(ERROR) << "Unable to start VoldNativeService";
exit(1);
    }

    //监听外部存储设备
if (nm->start()) {
        PLOG(ERROR) << "Unable to start NetlinkManager";
exit(1);
    }

    省略代码......

    android::IPCThreadState::self()->joinThreadPool();
    LOG(INFO) << "vold shutting down";

exit(0);
}


执行完上面的方法后,就代表具有root权限的系统native类型的vold进程 已经启动了,VoldNativeService是我的“接口人”,它是一个binder服务,Android进程的各位大佬们,如果你们想使用我提供的能力,请从ServiceManager中通过vold关键字可以找到我的binder代理,进而可以与VoldNativeService通信。


/   我的价值   /


你们人类一直在谈各种价值观,而我也不例外我也是有我的价值的,如果我没有价值,Android系统早把我干掉了。我的价值就是提供卷管理、挂载userdata分区、解密CE和DE类型目录、为app创建data和obb目录等能力,为建设一个健壮、高效的存储系统而努力。


/   卷管理   /


大家也都知道我的中文名字是卷守护进程,卷的管理那必当是我最重中之重的功能,既然是管理卷,那卷从何来呢?那就介绍给大家我是如何收集卷的吧。


卷从何来?


我vold管理的卷主要有三种:虚拟卷 (EmulatedVolume)、外部存储设备卷、obb卷。虚拟卷一般指的是真正访问目录为/data/media/的虚拟卷 (关于data/media的介绍可以看Android存储系统成长记文章) ,在android13上它的主要作用是提供一个虚拟的外部存储空间。外部存储设备卷就是指比如SD卡上的卷。关于obb卷不在这讨论。那就来介绍下虚拟卷和外部存储卷是从何而来。


对了真正管理所有卷的是NetlinkManager类,下面代码中的三个属性就对应了三种卷。




//文件路径:/system/vold/VolumeManager.h

//外部存储设备
std::list<std::shared_ptr<android::vold::Disk>> mDisks;
//obb卷
std::list<std::shared_ptr<android::vold::VolumeBase>> mObbVolumes;
//虚拟卷
std::list<std::shared_ptr<android::vold::VolumeBase>> mInternalEmulatedVolumes;




外部存储设备卷


那就来看下如何收集外部存储设备的卷,那就有请uevent、netlink、VolumeManager、NetlinkManager、NetlinkHandler它们出场,因为这个功能是它们共同实现的,先来看一个它们之间协同工作实现收集卷的功能图吧。



结合上图,收集外部存储设备卷的过程如下:


  1. 如插入SD卡,内核会发送uevent事件 (会携带设备的关键信息)

  2. NetLinkHandler把uevent事件发送给VolumeManager

  3. VolumeManager根据设备 (Disk)信息解析卷等信息

看了整个工作流程,那就有请各位来做下自我介绍,uevent先来吧。


uevent:“大家好,我的名字叫uevent,这个名字其实是 userspace event的缩写,翻译为中文是用户空间事件,你们肯定非常关心我的用途,我有非常重要的用途:那就是内核有关于设备相关的事件都会生成一个uevent,这个uevent会发送到用户空间,因此我的名字叫uevent。内核设备相关的事件有比如SD卡插入、删除等等。反正就是内核设备相关的事件都会发送一个uevent到用户空间。而发送是需要netlink这个通道的。”


netlink:“我是netlink,我的主要作用是搭建用户空间与内核空间的通道,官方对我的解释如下,请大家自行取阅。”


netlink 是 Linux 内核提供的一个用户空间与内核空间之间进行通信的机制。与传统的系统调用和 ioctl 相比,netlink 提供了更强大、更灵活的网络通信能力。它允许用户空间的应用程序与内核模块之间进行双向的、异步的通信


NetlinkManager:“大家好啊,我是NetlinkManager类,如netlink介绍我的主要作用就是用代码来实现内核空间与用户空间的通道,而我的启动是在vold进程启动的时候开始的。启动后我会把NetlinkHandler启动,那有请它。”


下面是NetlinkManager启动相关代码,请大家自行取阅。


//文件路径:/system/vold/main.cpp
//在vold进程fork成功后会执行main方法
int main(int argc, char** argv) {
    省略代码......
    NetlinkManager* nm;

    //获取NetlinkManager实例,它是单例
    if (!(nm = NetlinkManager::Instance())) {
        LOG(ERROR) << "Unable to create NetlinkManager";
        exit(1);
    }

    //启动NetlinkManager,它会与内核建立netlink通道
    if (nm->start()) { 
        PLOG(ERROR) << "Unable to start NetlinkManager";
        exit(1);
    }
    省略代码......

}

//文件路径:/system/vold/NetlinkManager.cpp
int NetlinkManager::start() {
    省略代码......
    
    //创建socket
    if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) < 0) {
        PLOG(ERROR) << "Unable to create uevent socket";
        return -1;
    }

    mHandler = new NetlinkHandler(mSock);
    //调用NetlinkHandler的start方法,这样内核有uevent事件,它就可以收到了
    if (mHandler->start()) {
        PLOG(ERROR) << "Unable to start NetlinkHandler";
        goto out;
    }

    return 0;

out:
    close(mSock);
    return -1;
}




NetlinkHandler:“大家好,我是NetlinkHandler类,通过名字也能看出我与netlink有关,我的主要作用就是拿到uevent事件后,传递给VolumeManager,那有请VolumeManager。”


VolumeManager:“我是VolumeManager类,大家上眼了看我的名字就知道我是干啥的,我管理着所有的卷 (volume)。我的启动同样也在vold进程启动的时候开始,下面是部分代码,可自行取阅。”



//文件路径:/system/vold/main.cpp
//在vold进程fork成功后会执行main方法
int main(int argc, char** argv) {
    省略代码......
    NetlinkManager* nm;

    /* Create our singleton managers */
    if (!(vm = VolumeManager::Instance())) {
        LOG(ERROR) << "Unable to create VolumeManager";
        exit(1);
    }

    if (!(nm = NetlinkManager::Instance())) {
        LOG(ERROR) << "Unable to create NetlinkManager";
        exit(1);
    }

    省略代码......
    //调用VolumeManager的start方法
    if (vm->start()) {
        PLOG(ERROR) << "Unable to start VolumeManager";
        exit(1);
    }
    省略代码......

}

//文件路径:/system/vold/VolumeManager.cpp
int VolumeManager::start() {

    省略代码......
    
    //卸载所有的卷,disk
    // Always start from a clean slate by unmounting everything in
    // directories that we own, in case we crashed.
    unmountAll();

    省略代码......
    
    //创建path为 /data/media 的虚拟卷
    auto vol = std::shared_ptr<android::vold::VolumeBase>(
            new android::vold::EmulatedVolume("/data/media"0));
    vol->setMountUserId(0);
    vol->create();
    //把虚拟卷放入mInternalEmulatedVolumes
    mInternalEmulatedVolumes.push_back(vol);

    省略代码......
    return 0;
}


VolumeManager接着说:“等我启动成功后,若有uevent事件,NetlinkHandler就会调用我的handleBlockEvent方法告知我,我就会把外部存储设备解析为Disk对象,Disk对象会创建对应的卷 (volume),下面是对应代码,可自行取阅。”



//文件路径:/system/vold/NetlinkHandler.cpp
//从kernel层有事件的话,会调用onEvent方法
void NetlinkHandler::onEvent(NetlinkEvent* evt) {
    VolumeManager* vm = VolumeManager::Instance();
    const char* subsys = evt->getSubsystem();

    省略代码......
    
    //只处理block类型的设备
    if (std::string(subsys) == "block") {
        //调用VolumeManager的handleBlockEvent方法
        vm->handleBlockEvent(evt);
    }
}

//文件路径:/system/vold/VolumeManager.cpp
void VolumeManager::handleBlockEvent(NetlinkEvent* evt) {
    std::lock_guard<std::mutex> lock(mLock);

    //获取设备相关信息
    std::string eventPath(evt->findParam("DEVPATH") ? evt->findParam("DEVPATH") : "");
    std::string devType(evt->findParam("DEVTYPE") ? evt->findParam("DEVTYPE") : "");
    //不是disk则返回
    if (devType != "disk"return;
    //获取disk的major、minor信息
    int major = std::stoi(evt->findParam("MAJOR"));
    int minor = std::stoi(evt->findParam("MINOR"));
    dev_t device = makedev(major, minor);

    switch (evt->getAction()) {
        //disk add事件
        case NetlinkEvent::Action::kAdd: {
            //只有mDiskSources内的才会被添加
            for (const auto& source : mDiskSources) {
                if (source->matches(eventPath)) {
                    省略代码......

                    //构建disk
                    auto disk =
                        new android::vold::Disk(eventPath, device, source->getNickname(), flags);
                    //处理disk add事件,查看下面handleDiskAdded方法
                    handleDiskAdded(std::shared_ptr<android::vold::Disk>(disk));
                    break;
                }
            }
            break;
        }
        case NetlinkEvent::Action::kChange: {
            LOG(VERBOSE) << "Disk at " << major << ":" << minor << " changed";
            //处理disk变化事件
            handleDiskChanged(device);
            break;
        }
        case NetlinkEvent::Action::kRemove: {
            //处理disk remove事件
            handleDiskRemoved(device);
            break;
        }
        default: {
            LOG(WARNING) << "Unexpected block event action " << (int)evt->getAction();
            break;
        }
    }
}

void VolumeManager::handleDiskAdded(const std::shared_ptr<android::vold::Disk>& disk) {
    //锁屏界面显示或者userid:0 还没有开始运行,则把disk添加到mPendingDisks,等待安全了在处理mPendingDisks的disk
    bool userZeroStarted = mStartedUsers.find(0) != mStartedUsers.end();
    if (mSecureKeyguardShowing) {
        LOG(INFO) << "Found disk at " << disk->getEventPath()
                  << " but delaying scan due to secure keyguard";
        mPendingDisks.push_back(disk);
    } else if (!userZeroStarted) {
        LOG(INFO) << "Found disk at " << disk->getEventPath()
                  << " but delaying scan due to user zero not having started";
        mPendingDisks.push_back(disk);
    } else {
        //若锁屏界面没显示并且userid:0 也开始运行了,则调用disk的create方法,create方法会创建卷信息,并且把disk添加到mDisks
        disk->create();
        mDisks.push_back(disk);
    }
}


小结


外部存储设备卷的收集主要的参与类是VolumeManager、NetlinkManager、NetlinkHandler,收集卷的过程:NetlinkManager负责创建netlink (它是内核空间与用户空间的通信通道),在netlink通信通道上传递的是uevent事件,如果内核有uevent事件会通知到NetlinkHandler,进而在通知到VolumeManager,VolumeManager根据是add、remove、change事件再去进一步的处理,如果是add事件则会创建Disk,Disk会创建对应的卷 (volume)。


不管是Disk的创建还是卷 (volume)的创建或者状态的改变都会把这样信息同步给StorageManagerService,而StorageManagerService则会把创建的Disk和卷信息保存起来,供app来使用。


虚拟卷


虚拟卷的对应类是EmulatedVolume,那就有请它来介绍下。


EmulatedVolume:“大家好,我是虚拟卷,在解释我的用处之前,先来介绍下外部存储,外部存储的文件是可以共享的,在android13上外部存储其实是一个从 /data 目录划分出一部分区域作为外部存储的,而划分出的区域的目录是 /data/media,也就是说这时候的外部存储是一个虚拟的外部存储。为了配合虚拟外部存储那就需要我来出马了,在vold进程启动或者设备user第一次运行的时候会创建path为 /data/media的EmulatedVolume。我虚拟卷也同样只有挂载了才能使用,而具体啥时候挂载则需要StorageManagerService来告知vold进程。”


同样虚拟卷的创建和状态变化也是需要通知给StorageManagerService,而StorageManagerService也会把虚拟卷保存下来,供app来使用。


下面是部分代码,自行取阅。



//文件路径:/system/vold/VolumeManager.cpp
//在VolumeManager的start方法会创建虚拟卷,vold进程启动的时候会调用start方法
int VolumeManager::start() {
    省略代码......
    
    //创建虚拟卷
    auto vol = std::shared_ptr<android::vold::VolumeBase>(
            new android::vold::EmulatedVolume("/data/media"0));
    vol->setMountUserId(0);
    vol->create();
    mInternalEmulatedVolumes.push_back(vol);

    // Consider creating a virtual disk
    updateVirtualDisk();

    return 0;
}


//在设备user第一次启动的时候会调用下面方法,而调用onUserStarted方法是StorageManagerService会通过binder调用来调用该方法
int VolumeManager::onUserStarted(userid_t userId) {
    LOG(INFO) << "onUserStarted: " << userId;
    
    //当前user没有启动,则开始执行createEmulatedVolumesForUser
    if (mStartedUsers.find(userId) == mStartedUsers.end()) {
        //调用下面的createEmulatedVolumesForUser方法
        createEmulatedVolumesForUser(userId);
    }

    mStartedUsers.insert(userId);

    createPendingDisksIfNeeded();
    return 0;
}

void VolumeManager::createEmulatedVolumesForUser(userid_t userId) {
    LOG(INFO) << "niu vold VolumeManager::createEmulatedVolumesForUser userId:" << userId;

    //创建目录为/data/media的虚拟卷
    // Create unstacked EmulatedVolumes for the user
    auto vol = std::shared_ptr<android::vold::VolumeBase>(
            new android::vold::EmulatedVolume("/data/media", userId));
    vol->setMountUserId(userId);
    mInternalEmulatedVolumes.push_back(vol);
    //调用create方法开始创建
    vol->create();

    省略代码......
}


总结


从外部存储设备卷和虚拟卷两个方向介绍了vold进程的管理的卷从何来,而具体管理卷的类是VolumeManager,vold进程管理的卷和Disk信息都会通知给StorageManagerService,StorageManagerService会把Disk和卷都保存起来,当app在往外部存储存储文件的时候,会通过binder调用从StorageManagerService获取可用卷,进而存储文件。


/   挂载userdata分区   /


介绍完毕卷管理,在来介绍我的另外一个重要功能挂载userdata分区,对应的能力接口是mountFstab,这个接口不当当只会挂载userdata分区,还会挂载别的分区。咱们现在只关心userdata分区的挂载。


fstab


在介绍挂载之前,先来介绍下fstab。


fstab : 是 Linux 和其他类 Unix 系统中的一个重要文件,它用于存储文件系统的静态信息。具体来说,fstab 文件列出了系统上所有的文件系统(包括交换分区)以及它们应该如何被挂载到文件系统中


如下是fstab内容的例子:




# Android fstab file.

#<src>                                                  <mnt_point>            <type>  <mnt_flags and options>                              <fs_mgr_flags>
# system分区挂载于 /system 目录
system                                                  /system                ext4    ro,barrier=1                                         wait,slotselect,avb=vbmeta_system,logical,first_stage_mount
system_ext                                              /system_ext            ext4    ro,barrier=1                                         wait,slotselect,avb=vbmeta_system,logical,first_stage_mount
vendor                                                  /vendor                ext4    ro,barrier=1                                         wait,slotselect,avb=vbmeta,logical,first_stage_mount
product                                                 /product               ext4    ro,barrier=1                                         wait,slotselect,avb,logical,first_stage_mount
/dev/block/by-name/metadata                             /metadata              ext4    noatime,nosuid,nodev,discard,data=journal,commit=1   wait,formattable,first_stage_mount,check
/dev/block/bootdevice/by-name/modem                     /vendor/firmware_mnt   vfat    ro,shortname=lower,uid=0,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait,slotselect
/dev/block/bootdevice/by-name/misc                      /misc                  emmc    defaults                                             defaults

# userdata分区挂载于 /data 目录
/dev/block/bootdevice/by-name/userdata                  /data                  f2fs    noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier       latemount,wait,check,quota,formattable,fileencryption=ice,reservedsize=128M,sysfs_path=/dev/sys/block/bootdevice,keydirectory=/metadata/vold/metadata_encryption,checkpoint=fs
/devices/platform/soc/a600000.ssusb/a600000.dwc3*       auto                   vfat    defaults                                             voldmanaged=usb:auto
/dev/block/zram0                                        none                   swap    defaults                                             zramsize=2147483648,max_comp_streams=8,zram_backingdev_size=512M



开始挂载


挂载userdata分区其实就是把 /dev/block/bootdevice/by-name/userdata (在上面的fstab文件中有定义)挂载到 /data目录,/data目录的作用主要是包含了各种各样的app数据、用户数据等等,如内部存储(根目录/data/user/userid)和外部存储(真正数据根目录 /data/media/userid) 都是位于/data目录下面(userid是当前设备的对应的用户id,如果不存在多用户,则它的值是0)。在如 /data/system目录下面存放了系统的各种jar、so库比如framework.jar就在这目录下面。


下面是data目录下面各种目录的一个截图,请自行取阅。



小结


data目录如此的重要,在设备启动的时候肯定是需要挂载userdata分区到/data目录的,这样才能通过/data目录访问各种数据,而挂载userdata分区的时机是在init进程启动的时候,挂载完毕后这时候/data目录下面的CE和DE类型的目录是处于加密状态的,只有解密了才能使用,那就来看下解密能力吧。


/   解密CD和DE类型目录   /


关于解密CE和DE类型目录的内容可以看Android存储系统成长记,就不在这赘述了。


/   为app创建data和obb目录   /


为app创建data和obb目录,为啥我要提供这个能力呢?


在解释原因之前,先说明一点,这里的data和obb目录是位于外部存储下面的 (外部存储的根目录是 /storage/emulated/userid) ,可不是位于内部存储 (内部存储的根目录是 /data/data 或者 /data/user/userid) 目录下面。而app的data和obb目录路径是:/storage/emulated/userid/Android/data/packagename和/storage/emulated/userid/Android/obb/packagename (packagename是包名) 。


data和obb目录是位于外部存储根目录下面,并且这俩目录有个特点就是它们的uid是与app的appid是一致的,也就是说只有当前app进程才可以访问自己的data和obb目录。虽然StorageManagerService位于systemserver进程,但是systemserver进程是没有为某个uid创建对应目录的权限的,而我vold进程是具有root权限的并且我也持有虚拟卷,因此为app创建data和obb目录的功能就被我“包揽”了。


下面是部分代码,请自行取阅。



//文件路径:/system/vold/VoldNativeService.cpp
//在创建app目录的时候会通过binder调用调用到该方法
binder::Status VoldNativeService::setupAppDir(const std::string& path, int32_t appUid) {
    ENFORCE_SYSTEM_OR_ROOT;
    CHECK_ARGUMENT_PATH(path);
    ACQUIRE_LOCK;
    //调用下面的setupAppDir方法
return translate(VolumeManager::Instance()->setupAppDir(path, appUid));
}

//path为要创建的目录,一般为/storage/emulated/userid/Android/data/packagename
int VolumeManager::setupAppDir(const std::string& path, int32_t appUid, bool fixupExistingOnly,
        bool skipIfDirExists) {
    // Only offer to create directories for paths managed by vold
    //不是以 /storage开头的则直接返回,表明它不是一个正常的外部存储路径
if (!StartsWith(path, "/storage/")) {
        LOG(ERROR) << "Failed to find mounted volume for " << path;
return -EINVAL;
    }

    // Find the volume it belongs to
    auto filter_fn = [&](const VolumeBase& vol) {
if (vol.getState() != VolumeBase::State::kMounted) {
            // The volume must be mounted
returnfalse;
        }
if (!vol.isVisibleForWrite()) {
            // App dirs should only be created for writable volumes.
returnfalse;
        }
if (vol.getInternalPath().empty()) {
returnfalse;
        }
if (vol.getMountUserId() != USER_UNKNOWN &&
            vol.getMountUserId() != multiuser_get_user_id(appUid)) {
            // The app dir must be created on a volume with the same user-id
returnfalse;
        }
if (!path.empty() && StartsWith(path, vol.getPath())) {
returntrue;
        }

returnfalse;
    };
    //查找到对应的卷,这个卷是EmulatedVolume (它的真正目录是 /data/media)
    auto volume = findVolumeWithFilter(filter_fn);
    //没找到返回
if (volume == nullptr) {
        LOG(ERROR) << "Failed to find mounted volume for " << path;
return -EINVAL;
    }
    // Convert paths to lower filesystem paths to avoid making FUSE requests for these reasons:
    // 1. A FUSE request from vold puts vold at risk of hanging if the FUSE daemon is down
    // 2. The FUSE daemon prevents requests on /mnt/user/0/emulated/<userid != 0> and a request
    // on /storage/emulated/10 means /mnt/user/0/emulated/10
    const std::string lowerPath =
            volume->getInternalPath() + path.substr(volume->getPath().length()); //niu volume->getInternalPath()为/data/media

    //volumeRoot为 /data/media/userid
    const std::string volumeRoot = volume->getRootPath();  // eg /data/media/0

    const int access_result = access(lowerPath.c_str(), F_OK);
if (fixupExistingOnly && access_result != 0) {
        // Nothing to fixup
return OK;
    }

if (skipIfDirExists && access_result == 0) {
        // It's safe to assume it's ok as it will be used for zygote to bind mount dir only,
        // which the dir doesn't need to have correct permission for now yet.
        return OK;
    }

    省略代码......

    //调用下面的PrepareAppDirFromRoot方法
    // Create the app paths we need from the root
    return PrepareAppDirFromRoot(lowerPath, volumeRoot, appUid, fixupExistingOnly);
}
int PrepareAppDirFromRoot(const std::string& path, const std::string& root, int appUid,
                          bool fixupExisting) 
{
    long projectId;
    size_t pos;
    int ret = 0;
    bool sdcardfsSupport = IsSdcardfsUsed();

    //root:/data/media/0,主要是创建 root+/Android root+/Android/data root+/Android/obb root+/Android/media
    // Make sure the Android/ directories exist and are setup correctly
    ret = PrepareAndroidDirs(root); 
    if (ret != 0) {
        LOG(ERROR) << "Failed to prepare Android/ directories.";
        return ret;
    }
    // Now create the application-specific subdir(s)
    // path is something like /data/media/0/Android/data/com.foo/files
    // First, chop off the volume root, eg /data/media/0
    std::string pathFromRoot = path.substr(root.length()); //niu pathFromRoot为/Android/data/com.foo/files

    uid_t uid = appUid;
    gid_t gid = AID_MEDIA_RW;
    std::vector<gid_t> additionalGids;
    std::string appDir;
    // Check that the next part matches one of the allowed Android/ dirs
    if (StartsWith(pathFromRoot, kAppDataDir)) {
        appDir = kAppDataDir; //niu /Android/data/
        if (!sdcardfsSupport) {
            gid = AID_EXT_DATA_RW;
            // Also add the app's own UID as a group; since apps belong to a group
            // that matches their UID, this ensures that they will always have access to
            // the files created in these dirs, even if they are created by other processes
            additionalGids.push_back(uid);
        }
    } elseif (StartsWith(pathFromRoot, kAppMediaDir)) {
        appDir = kAppMediaDir; //niu /Android/media
if (!sdcardfsSupport) {
            gid = AID_MEDIA_RW;
        }
    } elseif (StartsWith(pathFromRoot, kAppObbDir)) {
        appDir = kAppObbDir; //niu /Android/obb/
if (!sdcardfsSupport) {
            gid = AID_EXT_OBB_RW;
            // See comments for kAppDataDir above
            additionalGids.push_back(uid);
        }
    } else {
        LOG(ERROR) << "Invalid application directory: " << path;
return -EINVAL;
    }

    // mode = 770, plus sticky bit on directory to inherit GID when apps
    // create subdirs
    mode_t mode = S_IRWXU | S_IRWXG | S_ISGID;
    // the project ID for application-specific directories is directly
    // derived from their uid

    // Chop off the generic application-specific part, eg /Android/data/
    // this leaves us with something like com.foo/files/
    std::string leftToCreate = pathFromRoot.substr(appDir.length()); //niu packagename/files/
if (!EndsWith(leftToCreate, "/")) {
        leftToCreate += "/";
    }
    std::string pathToCreate = root + appDir; //niu /data/media/userid/Android/data
    int depth = 0;
    // Derive initial project ID
if (appDir == kAppDataDir || appDir == kAppMediaDir) {
        projectId = uid - AID_APP_START + PROJECT_ID_EXT_DATA_START;
    } elseif (appDir == kAppObbDir) {
        projectId = uid - AID_APP_START + PROJECT_ID_EXT_OBB_START;
    }

while ((pos = leftToCreate.find('/')) != std::string::npos) {
        std::string component = leftToCreate.substr(0, pos + 1);
        leftToCreate = leftToCreate.erase(0, pos + 1);
        pathToCreate = pathToCreate + component;

if (appDir == kAppDataDir && depth == 1 && component == "cache/") {
            // All dirs use the "app" project ID, except for the cache dirsin
            // Android/data, eg Android/data/com.foo/cache
            // Note that this "sticks" - eg subdirs of this dir need the same
            // project ID.
            projectId = uid - AID_APP_START + PROJECT_ID_EXT_CACHE_START;
        }

if (fixupExisting && access(pathToCreate.c_str(), F_OK) == 0) {
            // Fixup all files in this existing directory with the correct UID/GID
            // and project ID.
            ret = FixupAppDir(pathToCreate, mode, uid, gid, projectId);
        } else {
            ret = PrepareDirWithProjectId(pathToCreate, mode, uid, gid, projectId);
        }
if (ret != 0) {
return ret;
        }
if (depth == 0) {
            // Set the default ACL on the top-level application-specific directories,
            // to ensure that even if applications run with a umask of 0077,
            // new directories within these directories will allow the GID
            // specified here to write; this is necessary for apps like
            // installers and MTP, that require access here.
            //
            // See man (5) acl for more details.
            ret = SetDefaultAcl(pathToCreate, mode, uid, gid, additionalGids);
if (ret != 0) {
return ret;
            }
if (!sdcardfsSupport) {
                // Set project ID inheritance, so that future subdirectories inherit the
                // same project ID
                ret = SetQuotaInherit(pathToCreate);
if (ret != 0) {
return ret;
                }
            }
        }

        depth++;
    }
return OK;
}

/   总结   /


我是一个非常重要的具有root权限的系统native进程,由于我的性格生下来就低调、不热于表现,导致我不怎么出名,但是你们app确实一直在使用我提供的服务,如果没有我你们app就根本没有办法存储文件,因为存储文件是需要往内部存储或者外部存储下存储的,而内部存储和外部存储都需要挂载userdata分区到 /data 目录 (这可是我提供的能力)才达到了可用状态,即使挂载了也需要我提供的解密CE和DE类型目录的能力,要不 /data 目录下的这些目录都是加密的,加密状态下你们app绝对用不了的。


我管理着所有的卷,你们app在往外部存储存储文件的时候确实是从StorageManagerService获取的,但是别忘记StorageManagerService保存的卷的源头可是在我这。


这就是我的个人传记,希望在多年后大家能依然记得我,我毕竟在Android世界存在过。


推荐阅读:
我的新书,《第一行代码 第3版》已出版!
Android 15新特性,强制edge-to-edge全面屏体验
Android 文件系统小记


欢迎关注我的公众号

学习技术或投稿



长按上图,识别图中二维码即可关注

继续滑动看下一个
郭霖
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存